<html>
    <head>
        <title>总览</title>
    </head>
    <body>
        <script>
            // 依赖注入   Dependency Injection

                /*
                    ABP的依赖注入系统是基于 微软的"依赖注入扩展库" （Microsoft. Extentions.Dependency注入nuget包）开发的。
                    因此它的文档在ABP中也是有效的。

                    虽然ABP对任何第3方DI提供程序没有核心依赖。
                    但是，它需要使用支持动态代理和其他一些高级功能的提供程序来使某些ABP功能正常工作。
                    启动模板随附Autofac安装。有关详细信息，请参阅Autofac集成文档。
                */

                        /*
                            一、模块化
                                由于ABP是一个模块化框架，因此每个模块都通过依赖注入在自己单独的 "模块类"中定义的 "服务" 和 "注册器"：

                                    public class BlogModule : AbpModule
                                    {
                                        // 服务配置方法
                                        public override void ConfigureServices(ServiceConfigurationContext context)
                                        {
                                            // 在这里注册依赖的服务
                                        }
                                    }
                        */

                        /*
                            二、常规注册
                                    ABP引入了传统的服务注册。
                                    照惯例，你不需要做任何事情来注册服务。它是自动完成的。
                                    如果你想禁用它，你可以在模块类的构造函数中将模块类的 SkipAutoServiceRegistration 属性设置为 true:

                                        public class BlogModule : AbpModule
                                        {
                                            public BlogModule()
                                            {
                                                SkipAutoServiceRegistration = true; // 禁用自动服务注册
                                            }
                                        }

                                    跳过自动注册后，你应该手动注册你的服务。
                                    在这种情况下，使用扩展方法 AddAssemblyOf 可以帮助你按照约定注册所有服务。
                                    
                                    eg：
                                        public class BlogModule : AbpModule
                                        {
                                            // 在模块类的构造函数中禁用自动服务注册
                                            public BlogModule()
                                            {
                                                SkipAutoServiceRegistration = true;
                                            }

                                            public override void ConfigureServices(ServiceConfigurationContext context)
                                            {
                                                // 手动注册服务 （按照约定注册程序集中的所有服务）
                                                context.Services.AddAssemblyOf<BlogModule>();
                                            }
                                        } 

                                
                                    以下各节解释了约定和配置。
                        */

                        /*
                            三、框架预注册的类型 Inherently Registered Types
                                            默认情况下，某些特定类型注册到依赖注入：

                                                ①、模块类注册为单例模式。

                                                ②、MVC控制器 (继承自 Controller 或 AbpController)注册为瞬态牧师。

                                                ③、MVC页面模型（继承PageModel 或 AbpPageModel）注册为瞬态模式。

                                                ④、MVC视图组件 (继承ViewComponent 或 AbpViewComponent)被注册为瞬态模式。

                                                ⑤、应用程序服务 （继承ApplicationServie类 或其子类）注册为瞬态模式。

                                                ⑥、存储 （实现 IBasicRepositoryBase类 或其子类）注册为瞬态模式。

                                                ⑦、领域服务 (实现 IDomainService接口或继承 DomainService类)注册为瞬态模式。

                                        eg: 
                                            public class BlogPostAppService : ApplicationService
                                            {
                                            }

                                        BlogPostAppService自动注册瞬态生命周期，因为它派生自已知基类。
                          */

                          /*
                              四、依赖注入接口 Dependency Injection Interfaces
                              
                                        如果你先先这些接口，你的类将自动注册到依赖注入:
                                        
                                            ①、ITransientDependency
                                                    注册为瞬态模式。

                                            ②、ISingletonDependency
                                                    注册为单例模式。

                                            ③、IScopedDependency
                                                    注册为作用域模式。

                                        eg: 
                                            public class BlogPostAppService : ApplicationService, ITransientDependency
                                            {
                                            }

                                            因为它实现了ITransientDependency，所以会自动注册一个瞬态生命周期。
                           */
                        
                        /*
                            五、[Dependency] 特性
                                        为服务配置依赖注入的另一种方式是使用 依赖注入特性 [Dependency(ServiceLifetime.Transient,ReplaceServices =true)] 
                                        
                                        Dependency 具有以下属性：

                                        ①、Lifetime属性  服务的生命周期，值为： Singleton, Scoped 或 Transient。

                                        ②、TryRegister： 设置true以仅在之前未注册时注册服务。使用 “IServiceCollection”的  TryAdd…扩展方法。

                                        ③、ReplaceServices：  设置true以替换之前已经注册的服务。使用IServiceCollection的 Replace扩展方法。
                                                    
                                    eg: 
                                        [Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
                                        public class TaxCalculator
                                        {

                                        }

                                    Dependency属性如果定义了Lifetime属性，则它的优先级高于其他依赖接口
                        */

                        /*
                            六、[ExposeServices]特性  暴露服务 
                                    ExposeServices 特性用来控制当前类为哪些接口提供服务实现：

                                    [ExposeServices(typeof(ITaxCalculator))]
                                    public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency
                                    {

                                    }

                                    TaxCalculator类仅为ITaxCalculator接口提供服务实现的注入。
                                    这意味着你只能在应用程序中注入ITaxCalculator，但不能在应用程序中注入TaxCalculator或ICalculator。

                        */

                        /*
                            七、按照命名约定暴露服务

                                    如果你不指定要暴露（公开）哪些服务，ABP会按照约定公开服务。 
                                    所以，按照公约的话，上面的代码会把TaxCalculator类暴露为 ITaxCalculator的服务实现。
                                        
                                    ①、默认情况下，类本身是公开的。这意味着你可以通过TaxCalculator类注入它。

                                    ②、默认情况下，默认接口是公开的。默认接口由 "命名约定"确定。
                                       在上面的例子中，按照命名约定，ICalculator和ITaxCalculator是TaxCalculator的默认接口，但ICanCalculate不是。
                                       如果满足命名约定，通用接口(泛型接口)也被视为 "默认接口"

                                    ③、如果为Sigleton 和 Scoped 服务公开多个服务，则几诶西的实例将相同。此行为需要"公开类"本身。
                        */
                        
                        /*
                            八、将 [Dependency]特性、[ExposeServices]特性，以及按照命名约定暴露服务结合在一起使用：
                                    只要有意义，就可以组合属性和接口：

                                    [Dependency(ReplaceServices = true)]
                                    [ExposeServices(typeof(ITaxCalculator))]
                                    public class TaxCalculator: ITaxCalculator,ITransientDependency
                                    {
                                    
                                    }
                        */

                        /*
                            九、[ExposeKeyedService] 特性

                                    ExposeKeyedService特性，用于控制相关类提供哪些"键控服务"：

                                        [ExposeKeyedService<ITaxCalculator>("taxCalculator")]
                                        [ExposeKeyedService<ICalculator>("calculator")]
                                        public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency
                                        {

                                        }

                                    在上面的示例中，TaxCalculator类公开了ITaxCalculator接口和键taxCalculator
                                                   和ICalculator接口和键calculator。
                                    这意味着你可以从IServiceProvider获得键控服务：

                                        var taxCalculator = ServciceProvider.GetRequeiredKeyedService<ITaxCalculator>("taxCalculator");
                                        var calculator =ServiceProvider.GetRrequiredkeyedService<ICalculator>("calculator")；
                                    
                                    此外，你可以使用 [FromKeyedService]特性来解析构造函数中的某个"键控服务"：

                                            public class MyClass
                                            {
                                                // 注入键控服务
                                                public MyClass([FromKeyedServices("taxCalculator")] ITaxCalculator taxCalculator)
                                                {
                                                    TaxCalculator = taxCalculator;
                                                }
                                            }

                                    请注意， [ExposeKeyedService] 特性只是公开了键控服务。
                                    因此，如果不使用 [FromKeyedServices]特性，你就不能在应用程序中注入ITaxCaculator 或 ICalculator接口。
                                    如果你想同时公开键控服务和非键控服务，你可以一起使用 [ExposeService] 和 [ExposeKeyedServiceAttribute] 特性。
                                            
                                            [ExposeKeyedService<ITaxCalculator>("taxCalculator")]
                                            [ExposeKeyedService<ICalculator>("calculator")]
                                            [ExposeServices(typeof(ITaxCalculator), typeof(ICalculator))]
                                            public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency
                                            {
                                                
                                            }
                        */

                        /*
                            十、手动注册服务

                                    在某些情况下，你可能需要手动将服务注册到 IServiceCollection,特别是如果你需要使用自定义工厂方法或单例实例。
                                    在这种情况下，你可以按照Microsoft文档的描述直接添加服务：

                                            public class BlogModule : AbpModule
                                            {
                                                public override void ConfigureServices(ServiceConfigurationContext context)
                                                {
                                                    // 注册一个单例服务（与 asp.net core 中一样）
                                                    context.Services.AddSingleton<TaxCalculator>(new TaxCalculator(taxRatio: 0.18));

                                                    // 通过工厂方法注册服务
                                                    context.Services.AddScoped<ITaxCalculator>(
                                                        sp => sp.GetRequiredService<TaxCalculator>()
                                                    );
                                                }
                                            }
                        */

                        /*
                            十一、 替换服务
                                        
                                    如果你需要替换现有服务（由ABP或者其他模块依赖注册的服务），你有两种选择：
                                       
                                        1、使用ABP的 [Dependency] 特性，如上面所述。

                                        2、使用Microsoft依赖注入库的 IServiceCollection.Replace方法：
                                                public class MyModule : AbpModule
                                                {
                                                    public override void ConfigureServices(ServiceConfigurationContext context)
                                                    {
                                                        // 替换 IConnectionStringResolver 服务的实现
                                                        context.Services.Replace(
                                                            ServiceDescriptor.Transient<
                                                                IConnectionStringResolver,
                                                                MyConnectionStringResolver // 替换后的实现类
                                                            >());
                                                    }
                                                }  
                        */

                        /*
                            十二、如何注入服务
                                  
                                    1、构造函数注入：
                                            这是将服务注入类的最常用方法：
                                            public class TaxAppService : ApplicationService
                                            {
                                                private readonly ITaxCalculator _taxCalculator;

                                                public TaxAppService(ITaxCalculator taxCalculator)
                                                {
                                                    _taxCalculator = taxCalculator;
                                                }

                                                public async Task DoSomethingAsync()
                                                {
                                                    //...use _taxCalculator...
                                                }
                                            }

                                            构造函数注入是向类注入依赖项的首选方式。
                                            这样，除非提供所有constructor-injected依赖项，否则无法构造类。
                                            因此，类明确声明了它所需的服务。

                                    2、属性注入：
                                            Microsoft依赖注入库不支持属性注入。
                                            但是，ABP可以与第3方DI提供程序（例如Autofac）集成以使属性注入成为可能。
                                            eg:
                                                public class MyService : ITransientDependency
                                                {
                                                    public ILogger<MyService> Logger { get; set; }

                                                    public MyService()
                                                    {
                                                        Logger = NullLogger<MyService>.Instance; // Logger依赖的回退值。
                                                    }

                                                    public async Task DoSomethingAsync()
                                                    {
                                                        //...use Logger to write logs...
                                                    }
                                                }
                                            对于属性注入依赖项，你可以使用public setter声明一个公共属性。
                                            这允许DI框架在创建类后对其进行设置。

                                            属性注入的依赖项通常被认为是可选依赖项。这意味着没有它们，服务可以正常工作。
                                            Logger就是这样一个依赖项，MyService可以在没有日志记录的情况下继续工作。
                                            为了使依赖项“正确地可选”，我们通常为依赖项设置一个默认/回退值。

                                            当你想设计一个默认注入了一些公共服务的基类时，属性注入也很有用。
                                            如果你打算使用构造函数注入，所有派生类也应该将依赖的服务注入到他们的恶狗早函中，这使得开发变得困难。
                                            但是，对非可选服务使用属性注入要非常小心。

                                    3、[DisablePropertyInjection] 特性
                                            你可以对类货期属性使用 [DisablePropertyInjection] 特性来禁用整个类或者属性的属性注入：
                                              
                                                // 禁用 MyService 类中所有属性的属性注入
                                                [DisablePropertyInjection]
                                                public class MyService : ITransientDependency
                                                {
                                                    public ILogger<MyService> Logger { get; set; }
                                                    
                                                    public ITaxCalculator TaxCalculator { get; set; }
                                                }

                                                // 只禁用 TaxCalculator 属性的属性注入
                                                public class MyService : ITransientDependency
                                                {
                                                    public ILogger<MyService> Logger { get; set; }

                                                    [DisablePropertyInjection] // 禁用针对当前属性的属性注入
                                                    public ITaxCalculator TaxCalculator { get; set; }
                                                }
                                    
                                    4、属性注入服务 IInjectPropertiesService
                                             可以使用  IInjectPropertiesService 服务来注入对象的属性，一般是DI之外的服务，比如手动创建的服务：
                                                
                                                var injectPropertieService = serviceProvider.GetRequiredService<IInjectPropertiesService>(); // 获取注入属性的服务
                                                var instance = new MyService(); // 需要被属性注入的服务实例

                                                injectPropertiesService.InjectProperties(instance); // 实例上任何可以被IServiceProvider解析的属性都会被注入。

                                                // 在实例上设置任何可以由服务提供程序（IServiceProvider）解析的空值属性
                                                injectPropertiesService.InjectUnsetProperties(instance); // 实力上任何可以被IServiceProvider解析的空值属性多会被注入。
                        */

                        /*
                            十三、从IServiceProvider解析服务
                                    你可能希望直接从IServiceProvider解析服务。
                                    这时，你可以将IServiceProvider注入到你的类中，并且使用GetService()方法 或者 GetRequiredService() 方法：
                                    
                                                public class MyService : ITransientDependency
                                                {
                                                    private readonly ITaxCalculator _taxCalculator;

                                                    public MyService(IServiceProvider serviceProvider) // 构造函数注入 IServiceProvider (IOC容器服务提供者程序)
                                                    {
                                                        // 通过当前IOC容器服务提供者实例直接获取特定服务
                                                        _taxCalculator = serviceProvider.GetRequiredService<ITaxCalculator>();
                                                    }
                                                }
                        */

                        /*
                            十四、处理多实现

                                    你可以注册统一服务接口的多个实现。假设你有一个 IExternalLogger 接口，具有两个实现：
                                                
                                                // 声明 IExterbalLogger 接口
                                                public interface IExternalLogger
                                                {
                                                    Task LogAsync(string logText);
                                                }

                                                // 第一个实现类
                                                public class ElasticsearchExternalLogger : IExternalLogger
                                                {
                                                    public async Task LogAsync(string logText)
                                                    {
                                                        //TODO...
                                                    }
                                                }

                                                // 第二个实现类
                                                public class AzureExternalLogger : IExternalLogger
                                                {
                                                    public Task LogAsync(string logText)
                                                    {
                                                        throw new System.NotImplementedException();
                                                    }
                                                }

                                    在这个例子中，我们还没有讲任何实现类注册到依赖注入系统。
                                    一次，如果我们尝试注入 IExternalLogger接口，我们会得到一个错误，表明没有找到实现。

                                    如果我们为IExternalLogger接口注册 ElasticsearchExternalLogger 和 AzureExternalLogger，然后尝试注入 IExternalLogger，
                                    则将使用最后注册的实现。

                                    注入IExternalLogger接口的示例服务：
                                            public class MyService : ITransientDependency
                                            {
                                                private readonly IExternalLogger _externalLogger;

                                                public MyService(IExternalLogger externalLogger)
                                                {
                                                    _externalLogger = externalLogger;
                                                }

                                                public async Task DemoAsync()
                                                {
                                                    await _externalLogger.LogAsync("Example log message...");
                                                }
                                            }  
                                    在这里，如前所述，我们得到最后一个注册的实现。但是，如何确定最后一个注册的实现？  
                                    如果我们实现了其中一个依赖接口（例如ITransientDependency），那么注册顺序将是不确定的（它可能取决于类的命名空间）。
                                    最后注册的实现可能与你的预期的不同。因此，不建议使用依赖接口来注册多个实现。  
                                    
                                    
                                    你可以在模块的 ConfigureServices() 方法中注册你的服务：

                                            public override void ConfigureServices(ServiceConfigurationContext context)
                                            {
                                                context.Services.AddTransient<IExternalLogger, ElasticsearchExternalLogger>();
                                                context.Services.AddTransient<IExternalLogger, AzureExternalLogger>();
                                            }

                                    在这种情况下，当你注入IExternalLogger接口时，你会得到一个AzureExternalLogger实例，因为最后注册的实现是AzureExternalLogger类。

                                    当你有一个借口的多个实现时，你可能希望使用所有这些实现。
                                    假设你要将日志写入所有外部记录器。我们可以将MyService实现更改为以下内容：

                                            public class MyService : ITransientDependency
                                            {
                                                // 注入 IEnumerable<IExternalLogger> 而不是 IExternalLogger。
                                                private readonly IEnumerable<IExternalLogger> _externalLoggers;

                                                public MyService(IEnumerable<IExternalLogger> externalLoggers)
                                                {
                                                    _externalLoggers = externalLoggers;
                                                }

                                                public async Task DemoAsync()
                                                {
                                                    // 遍历所有外部记录器并写入日志。
                                                    foreach (var externalLogger in _externalLoggers)
                                                    {
                                                        await externalLogger.LogAsync("Example log message...");
                                                    }
                                                }
                                            }

                                    在这个例子中，我们注入了 "IEnumerable<IExternalLogger>"  而不是IExternalLogger，
                                    所以，我们有了一个IExternalLogger 接口的所有已注册实现的集合。
                                    然后我们使用foreach循环将相同的日志文本写入所有IExternalLogger实现。

                                    如果你是用IServiceProvider来解析依赖关系，则使用其 GetServices 方法来获取服务的实现集合：

                                        IEnumerable<IExternalLogger> services = _serviceProvider.GetServices<IExterbalLogger>();
                    */

                    /*
                        十五、IServiceCollection （IOC容器） 和 IServiceProvider的关系
                                    
                                
                                1、IServiceCollection
                                        IServiceCollection 是一个接口，用于表示服务的集合。
                                        在应用启动时，你会借助它来注册应用所需的各项服务。
                                        本质上，它是一个服务注册容器，能让你把服务的抽象类型（一般是接口）和具体实现类型关联起来，还能设定服务的生命周期。
                                                
                                                using Microsoft.Extensions.DependencyInjection; // 引入依赖注入相关命名空间

                                                var services = new ServiceCollection(); // 创建 IServiceCollection 的实例
                                                // 注册服务
                                                services.AddTransient<IMyService, MyService>(); // 注册一个 transient 服务


                                2、IServiceProvider

                                        IServiceProvider 也是一个接口，其作用是提供服务实例。
                                        在注册完所有服务到 IServiceCollection 之后，你可以调用 BuildServiceProvider 方法来创建一个 IServiceProvider 实例。
                                        IServiceProvider 能够依据注册的服务信息，解析并提供所需的服务实例。
                                                
                                                using Microsoft.Extensions.DependencyInjection; // 引入依赖注入相关命名空间

                                                var services = new ServiceCollection(); // 创建 IServiceCollection 的实例
                                                services.AddTransient<IMyService, MyService>(); // 注册一个 transient 服务

                                                // 创建 IServiceProvider 实例
                                                var serviceProvider = services.BuildServiceProvider();

                                                // 解析服务
                                                var myService = serviceProvider.GetService<IMyService>();
                                
                                3、二者关系
                                        先后顺序：
                                              线利用IServiceCollection 注册服务，之后再基于IServiceProvider实例。
                                        
                                        协作配合：
                                              IserviceCollection负责服务的注册，而IServiceProvider负责服务的解析。
                                              IServiceProvider依据 IServiceCollection中注册的服务信息，提供服务实例。

                                        综上所述，IServiceCollection是服务注册的起点，而 IServiceProvider则是服务解析的重点，它们共同构成了依赖注入的核心机制。
                    */

                    /*
                        十六、服务的释放
                                    如果你使用了构造函数或属性注入，则无需担心释放服务的资源。
                                    但是，你已经从 IServiceProvider 解析了服务，在某些情况下，你可能需要注意释放服务资源。

                                    ASP.NETCore在当前HTTP请求结束时释放所有服务，即使你直接从IServiceProvider解析（假设你注入了IServiceProvider）。
                                    但是，在一些情况喜下，你可能希望释放手动解析的服务：

                                            1、你的代码在ASP.NET核心请求之外执行，执行程序尚未处理服务范围

                                            2、你有对根服务提供者的引用

                                            3、你可能希望立即释放和处置服务（例如，你可能会创建太多使用大量内存的服务，并且不想过度使用内存）

                                    在任何情况下，你都可以创建一个服务作用域块来安全地立即释放服务：
                                            using (var scope = _serviceProvider.CreateScope())
                                            {
                                                var service1 = scope.ServiceProvider.GetService<IMyService1>();
                                                var service2 = scope.ServiceProvider.GetService<IMyService2>();
                                            }
                                            当创建的作用域被释放时（在using块的末尾），这两个服务都会被释放。
                    */

                    /*
                        十七、带有缓存的ServiceProvider 
                                   ABP提供了两个特殊服务来优化来自IServiceProvider的解析服务。
                                   ICachedServiceProvider和ITransientCachedServiceProvider都继承自IServiceProvider接口，
                                   并在内部缓存已解析的服务，因此即使你多次解析服务，你也可以获得相同的服务实例。

                                   主要区别在于ICachedServiceProvider本身注册为作用域，而ITransientCachedServiceProvider注册为依赖注入系统的瞬态。

                                   以下示例注入ICachedServiceProvider服务，并在DoSomethingAsync方法中解析服务：

                                            public class MyService : ITransientDependency
                                            {
                                                private readonly ICachedServiceProvider _serviceProvider;


                                                public MyService(ICachedServiceProvider serviceProvider)
                                                {
                                                    _serviceProvider = serviceProvider;// 注入带有缓存的服务提供者服务
                                                }

                                                public async Task DoSomethingAsync()
                                                {
                                                    // 使用带有缓存的服务提供者来解析服务
                                                    var taxCalculator = _serviceProvider.GetRequiredService<ITaxCalculator>();
                                                    
                                                }
                                            } 
                                                
                                    使用这样的用法，你不需要释放创建的服务作用域范围和释放已解析的服务（如上面的发布/处置服务部分所解释的）。
                                    因为一旦释放了MyService实例的服务范围，从ICachedServiceProvider解析的所有服务都将被释放。
                                    此外，你不需要关心内存泄漏（因为如果我们调用DoSomethingAsync太多次，会创建太多的ITaxCalculator实例），因为只创建了一个ITaxCalculator实例，并且它被重用。

                                    由于ICachedServiceProvider和ITransientCachedServiceProvider扩展了标准的IServiceProvider接口，
                                    你可以在它们身上使用IServiceProvider接口的所有“扩展方法”。
                                    此外，它们还提供了一些其他方法来为未找到的服务提供默认值或工厂方法（即未注册到依赖注入系统）。
                                    请注意，默认值（或从你的工厂方法返回的值）也被缓存并重用。

                                    使用ICachedServiceProvider（而不是ITransientCachedServiceProvider），除非你需要每次使用创建服务缓存。
                                    ITransientCachedServiceProvider保证创建的服务实例不与任何其他服务共享，即使它们在同一个服务范围内。
                                    从ICachedServiceProvider解析的服务与同一服务范围内的其他服务共享（例如在同一个HTTP请求中），因此可以认为它更优化。
                    */
        </script>
    </body>
</html>